package com.spzx.order.service.impl;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.cart.api.domain.CartInfo;
import com.spzx.cart.api.service.RemoteCartService;
import com.spzx.common.core.constant.SecurityConstants;
import com.spzx.common.core.domain.R;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.StringUtils;
import com.spzx.common.rabbit.constant.MqConst;
import com.spzx.common.rabbit.service.RabbitService;
import com.spzx.common.security.utils.SecurityUtils;
import com.spzx.order.api.domain.OrderInfo;
import com.spzx.order.api.domain.OrderItem;
import com.spzx.order.domain.*;
import com.spzx.order.mapper.OrderItemMapper;
import com.spzx.order.mapper.OrderLogMapper;
import com.spzx.order.service.IOrderItemService;
import com.spzx.product.api.domain.ProductSku;
import com.spzx.product.api.domain.vo.SkuLockVo;
import com.spzx.product.api.service.RemoteProductService;
import com.spzx.product.api.service.RemoteSkuStocktService;
import com.spzx.user.api.domain.UserAddress;
import com.spzx.user.api.service.RemoteUserAddressService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import com.spzx.order.mapper.OrderInfoMapper;
import com.spzx.order.service.IOrderInfoService;
import org.springframework.util.CollectionUtils;

/**
 * 订单Service业务层处理
 *
 * @author atguigu
 * @date 2024-11-02
 */
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements IOrderInfoService
{
    @Autowired
    private OrderInfoMapper orderInfoMapper;
    @Autowired
    private OrderItemMapper orderItemMapper;

    @Autowired
    private RemoteCartService remoteCartService;

    @Autowired
    private RemoteProductService remoteProductService;

    @Autowired
    private RemoteUserAddressService remoteUserAddressService;


    @Autowired
    private RemoteSkuStocktService remoteSkuStocktService;

    @Autowired
    private IOrderItemService orderItemService;

    @Autowired
    private OrderLogMapper orderLogMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RabbitService rabbitService;
    /**
     * 查询订单列表
     *
     * @param orderInfo 订单
     * @return 订单
     */
    @Override
    public List<OrderInfo> selectOrderInfoList(OrderInfo orderInfo)
    {
        return orderInfoMapper.selectOrderInfoList(orderInfo);
    }

    @Override
    public OrderInfo getOrderById(Long id) {
       /* //方案一：根据订单id查询订单信息，再根据订单id查询订单项信息，然后将订单项设置到订单中
        OrderInfo orderInfo = baseMapper.selectById(id);
        //根据订单id查询订单对应的订单项
        List<OrderItem> orderItems = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>().eq(OrderItem::getOrderId, id));
        //将订单项设置到订单中
        orderInfo.setOrderItemList(orderItems);*/


        OrderInfo orderInfo = baseMapper.getOrderById(id);
        return orderInfo;
    }

    @Override
    public TradeVo trade() {
        //获取用户id
        Long userId = SecurityUtils.getUserId();
        //远程调用remoteCartService获取用户已选中的购物项
        R<List<CartInfo>> cartCheckedInfo = remoteCartService.getCheckedCartInfo(userId, SecurityConstants.INNER);
        if (R.FAIL==cartCheckedInfo.getCode()){
            //抛出异常
            throw new ServiceException("远程调用购物车获取已选中的所有的购物项失败");
        }

        //创建一个返回的对象
        TradeVo tradeVo = new TradeVo();

        //获取所有已经选中的购物项
        List<CartInfo> cartInfoList = cartCheckedInfo.getData();
        //设置一个集合
        List<OrderItem> orderItemList=null;

        //验空
        if (!CollectionUtils.isEmpty(cartInfoList)){
            //将List<CartInfo>转换为List<OrderItem>
             orderItemList = cartInfoList.stream().map(cartInfo -> {
                //创建一个OdedrItem对象
                 OrderItem orderItem = new OrderItem();
                //复制属性值
                BeanUtils.copyProperties(cartInfo, orderItem);
                return orderItem;
            }).toList();
            //设置结算商品列表
            tradeVo.setOrderItemList(orderItemList);

            //设置总金额
            BigDecimal totalAmount =new BigDecimal("0");

            for (OrderItem orderItem : orderItemList) {
                //商品的价格
                BigDecimal skuPrice = orderItem.getSkuPrice();
                //商品的数量,并将商品的数量转换为BigDecimal类型
                BigDecimal bigDecimal = new BigDecimal(orderItem.getSkuNum() + "");

                totalAmount=totalAmount.add(skuPrice.multiply(bigDecimal));
            }
            //设置总金额
            tradeVo.setTotalAmount(totalAmount);
            //使用UUID生成的随机字符串作为交易号
            String tradeNo= UUID.randomUUID().toString().replaceAll("-","");
            //设置交易号
            tradeVo.setTradeNo(tradeNo);
            //将交易号添加到Redis中
            this.addTradeNotRedis(userId,tradeNo);

        }else {
            throw new ServiceException("购物车为空");
        }

        return tradeVo;
    }

    //立即购买
    @Override
    public TradeVo buy(Long skuId) {
        //创建返回对象
        TradeVo tradeVo = new TradeVo();
        //远程调用商品微服务查询商品sku信息
        R<ProductSku> productSkuR = remoteProductService.getProductSku(skuId, SecurityConstants.INNER);
        if (R.FAIL==productSkuR.getCode()){
            throw new ServiceException("调用失败");
        }
        //获取商品信息
        ProductSku productSku = productSkuR.getData();
        //创建集合
        List<OrderItem> orderItemList=new ArrayList<>();
        OrderItem orderItem = new OrderItem();
        orderItem.setSkuId(skuId);
        orderItem.setSkuNum(1);
        orderItem.setSkuName(productSku.getSkuName());
        orderItem.setSkuPrice(productSku.getSalePrice());
        orderItem.setThumbImg(productSku.getThumbImg());
        orderItemList.add(orderItem);
        //使用UUID随机生成一个字符串作为交易号
        String tradeNo = UUID.randomUUID().toString().replaceAll("-", "");

        //获取用户id
        Long userId = SecurityUtils.getUserId();
        //将交易号添加到Redis中
        this.addTradeNotRedis(userId,tradeNo);




        //给tradeVo设置属性值
        tradeVo.setOrderItemList(orderItemList);
        tradeVo.setTotalAmount(productSku.getSalePrice());
        tradeVo.setIsBuy(true);
        tradeVo.setTradeNo(tradeNo);
        return tradeVo;
    }

    //
    @Override
    public Long submitOrder(OrderForm orderForm) {
        //获取交易号
        String tradeNo = orderForm.getTradeNo();
        try {
            //获取用户id和登录的用户名
            Long userId = SecurityUtils.getUserId();
            String nickName = SecurityUtils.getUsername();
            //获取提交的数据
            Long userAddressId = orderForm.getUserAddressId();

            //获取运费
            BigDecimal feightFee = orderForm.getFeightFee();
            //获取备注
            String remark = orderForm.getRemark();
            //获取是否是立即购买
            Boolean isBuy = orderForm.getIsBuy();

            //防止重复下单
            //使用lua解决原子性的问题
            String userTradeKey = "user:tradeNo:" + userId;
            //将键的值更改为processed 表示已经处理
            // return 1 表示处理成功
            // return -1  键存在但值不匹配，表示重复提交
            // return 0   键不存在，订单过期
            String sciptTest= """
             if redis.call('get',KEYS[1]) == ARGV[1] then
                 redis.call('set',KEYS[1],'processed')
                 return 1
             else
                 if redis.call('exists',KEYS[1]) == 1 then
                    redis.call('del',KEYS[1])
                    return -1
                 else
                    return 0
                end
            end
                    """;

            DefaultRedisScript<Long> longDefaultRedisScript=new DefaultRedisScript<>();
            //设置执行的lua脚本
            longDefaultRedisScript.setScriptText(sciptTest);
            //设置结果类型
            longDefaultRedisScript.setResultType(Long.class);
            //执行lua脚本
            Long flag = (Long) redisTemplate.execute(longDefaultRedisScript, Arrays.asList(userTradeKey), tradeNo);
            //判断
            if (flag==-1){
                throw new ServiceException("老毕登,鼠标扣慢点,已经下单成功了,请勿重复提交!");
            }
            if (flag==0){
                throw new ServiceException("订单已过期");
            }

            //获取所有的订单项
            List<OrderItem> orderItemList = orderForm.getOrderItemList();

            //校验价格
            //遍历所有的订单项
            orderItemList.forEach(orderItem -> {
                //远程调用微服务根据skuid获取商品最新价格
                R<ProductSku> productSkuData = remoteProductService.getProductSku(orderItem.getSkuId(), SecurityConstants.INNER);
                //验空
                if (R.FAIL==productSkuData.getCode()){
                    throw new ServiceException(productSkuData.getMsg());
                }
                //获取商品信息
                ProductSku productSku = productSkuData.getData();
                //获取商品最新价格
                BigDecimal salePrice = productSku.getSalePrice();
                //获取购物车里的价格
                BigDecimal orderItemSkuPrice = orderItem.getSkuPrice();
                //判断当前购物车里的价格与最新价格是否相等
                if (orderItemSkuPrice.longValue()!=salePrice.longValue()){
                    throw new ServiceException(orderItem.getSkuName()+"价格发生了变化,请返回购物车重新下单");
                }
            });


            /**
             * 锁库存
             */
            //将List<OrderItem>转换为List<SkuLockVo>
            List<SkuLockVo> skuLockVoList = orderItemList.stream().map(orderItem -> {
                SkuLockVo skuLockVo = new SkuLockVo();
                //设置属性值
                skuLockVo.setSkuId(orderItem.getSkuId());
                skuLockVo.setSkuName(orderItem.getSkuName());
                skuLockVo.setSkuNum(orderItem.getSkuNum());

                return skuLockVo;
            }).toList();
            //远程调用商品微服务,检查和锁定库存
            R<String> stringR = remoteSkuStocktService.checkAndLockStock(tradeNo, skuLockVoList, SecurityConstants.INNER);
            if (R.FAIL==stringR.getCode()){
                throw new ServiceException("");
            }
            //获取返回值
            String lcokMessage = stringR.getData();
            if (StringUtils.hasText(lcokMessage)){
                //证明有库存不足的商品
                throw new ServiceException(lcokMessage);
            }


            //创建OrderInfo对象
            OrderInfo orderInfo = new OrderInfo();
            orderInfo.setUserId(userId);
            orderInfo.setNickName(nickName);
            orderInfo.setOrderNo(tradeNo);
            orderInfo.setFeightFee(feightFee);
            orderInfo.setRemark(remark);
            //设置总金额
            BigDecimal totalAmount = new BigDecimal(0);
            //遍历所有的订单项
            for (OrderItem orderItem : orderItemList) {
                totalAmount = totalAmount.add(orderItem.getSkuPrice().multiply(new BigDecimal(orderItem.getSkuNum())));
            }
            //设置订单金额
            orderInfo.setTotalAmount(totalAmount);
            //设置原价金额
            orderInfo.setOriginalTotalAmount(totalAmount);
            //订单状态
            orderInfo.setOrderStatus(0);
            //远程调用微服务RemoteUserAddressService获取收获地址
            R<UserAddress> userAddressByUserId = remoteUserAddressService.getUserAddressByUserId(userAddressId, SecurityConstants.INNER);
            if (R.FAIL==userAddressByUserId.getCode()){
                throw new ServiceException("获取收货地址失败");
            }
            //获取收获地址
            UserAddress userAddress = userAddressByUserId.getData();
            //保存用户地址信息
            orderInfo.setReceiverName(userAddress.getName());
            orderInfo.setReceiverPhone(userAddress.getPhone());
            orderInfo.setReceiverTagName(userAddress.getTagName());
            orderInfo.setReceiverProvince(userAddress.getProvinceCode());
            orderInfo.setReceiverCity(userAddress.getCityCode());
            orderInfo.setReceiverDistrict(userAddress.getDistrictCode());
            orderInfo.setReceiverAddress(userAddress.getFullAddress());

            //数据库插入订单信息
            baseMapper.insert(orderInfo);


            //获取订单id
            Long orderId = orderInfo.getId();
            //给订单项设置id
            orderItemList = orderItemList.stream().map(orderItem -> {
               orderItem.setOrderId(orderId);
               return orderItem;
           }).toList();
            //批量插入订单项
            orderItemService.saveBatch(orderItemList);


            //创建日志OrderLog
            OrderLog orderLog = new OrderLog();
            orderLog.setOrderId(orderId);
            orderLog.setProcessStatus(0);
            orderLog.setOperateUser("用户");
            orderLog.setNote("提交订单");

            //插入日志
            orderLogMapper.insert(orderLog);

            if (!isBuy){
                //远程调用购物车微服务清除选中的购物项
                remoteCartService.clearCheckedCartInfo(userId, SecurityConstants.INNER);
            }

            return orderId;
        } catch (ServiceException e) {
            //释放库存,向RabbitMq发送消息释放库存
            rabbitService.sendMessage(MqConst.EXCHANGE_PRODUCT,MqConst.ROUTING_UNLOCK,tradeNo);

        }
        return null;

    }

    //查询用户订单
    @Override
    public List<OrderInfo> getUserOtders(String orderStatus) {
        //方案一:分别查询订单和订单项,然后将订单和订单项设置到订单中
        //方案二:查询订单的同时将订单项也查询出来(联合查询),分页不能用联合查询
        //方案三:查询订单的同时将订单项也查询出来(分步查询)
        List<OrderInfo> orderInfoList= baseMapper.getUserOrdersByUserIdAndOredrStatus(SecurityUtils.getUserId(),orderStatus);
        return orderInfoList;
    }

    //取消订单
    @Override
    public void cancelOrder(Long orderId) {
        //创建订单对象
        OrderInfo orderInfo = new OrderInfo();
        //设置主键
        orderInfo.setId(orderId);
        orderInfo.setCancelTime(new Date());
        orderInfo.setCancelReason("用户自主取消订单");
        orderInfo.setOrderStatus(-1);
        orderInfo.setUpdateTime(new Date());

        //调用根据更新的方法
        baseMapper.updateById(orderInfo);

        //创建日志对象
        OrderLog orderLog = new OrderLog();
        orderLog.setOrderId(orderId);
        orderLog.setNote("用户取消订单");
        orderLog.setUpdateTime(new Date());
        orderLog.setProcessStatus(-1);
        orderLog.setOperateUser("用户");
        orderLog.setUpdateBy(SecurityUtils.getUsername());
        //插入日志
        orderLogMapper.insert(orderLog);

        //根据id查询订单id
        OrderInfo orderInfo1 = baseMapper.selectById(orderId);
        String orderNo = orderInfo1.getOrderNo();
        //向RabbitMq发送消息释放库存
        //释放库存,向RabbitMq发送消息释放库存
        rabbitService.sendMessage(MqConst.EXCHANGE_PRODUCT,MqConst.ROUTING_UNLOCK,orderNo);


    }

    //根据订单号获取订单的信息
    @Override
    public OrderInfo getOrderInfoByOrderNo(String orderNo) {
        //调用带条件的查询
        OrderInfo orderInfo = baseMapper.selectOne(new LambdaQueryWrapper<OrderInfo>().eq(OrderInfo::getOrderNo, orderNo));
        //调用orderItemMapper中根据订单id查询订单项的方法
        List<OrderItem> orderItemListByOrderId = orderItemMapper.getOrderItemListByOrderId(orderInfo.getId());
        //设置订单项
        orderInfo.setOrderItemList(orderItemListByOrderId);
        return orderInfo;
    }

    //更新订单状态
    @Override
    public void updateOrderInfoStatus(String orderNo) {
        //调用带条件的更新的方法
        baseMapper.update(null,new LambdaUpdateWrapper<OrderInfo>().set(OrderInfo::getOrderStatus,1).eq(OrderInfo::getOrderNo, orderNo));
        //根据订单号查询订单信息
        OrderInfo orderInfo = baseMapper.selectOne(new LambdaQueryWrapper<OrderInfo>().eq(OrderInfo::getOrderNo, orderNo));
        //插入订单日志
        OrderLog orderLog = new OrderLog();
        //设置属性值
        orderLog.setOrderId(orderInfo.getId());
        orderLog.setNote("支付成功");
        orderLog.setOperateUser("用户");
        orderLog.setProcessStatus(1);
        orderLog.setCreateBy(SecurityUtils.getUsername());
        //插入订单日志
        orderLogMapper.insert(orderLog);

    }

    //去结算或立即购买时向Redis中存放交易号的方法
    public void addTradeNotRedis(Long userId,String tradeNo){
        //向Redis中存放交易号的Key
        String redisKey="user:tradeNo:"+userId;
        //将交易号保存到Redis中,并设置5分钟的有效时间
        redisTemplate.opsForValue().set(redisKey,tradeNo,5, TimeUnit.MINUTES);
    }
}
